package com.mt.mall.service.skill.impl;

import com.alibaba.fastjson.JSONObject;
import com.mt.common.core.security.RedisUtil;
import com.mt.common.core.utils.PrimaryGenerator;
import com.mt.common.core.utils.RabbitMqUtil;
import com.mt.common.core.utils.RedissonUtil;
import com.mt.common.core.web.JsonResult;
import com.mt.mall.dao.Order.OrderDao;
import com.mt.mall.dao.skill.SkillCommodityDao;

import com.mt.common.core.exception.BusinessException;
import com.mt.common.core.web.base.PageDTO;
import com.mt.common.core.web.base.PageResultDTO;
import com.mt.common.core.web.base.BaseEntity;
import com.mt.common.core.web.BaseService;
import com.mt.mall.entity.Order.Order;
import com.mt.mall.entity.User.ShippingAddress;
import com.mt.mall.service.good.SpecificationStockService;
import io.lettuce.core.RedisClient;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.mt.mall.entity.skill.SkillCommodity;
import com.mt.mall.service.skill.SkillCommodityService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
@Transactional
public class SkillCommodityServiceBean extends BaseService implements SkillCommodityService {

    private static Logger logger = LogManager.getLogger();

	@Autowired
	private SkillCommodityDao skillCommodityDao;

	@Resource
	private RedisTemplate<String, List<SkillCommodity>> redisTemplate;

	@Autowired
	StringRedisTemplate stringRedisTemplate;


	@Autowired
	private RedisUtil redisUtil;
	/**
	 * 根据分页参数查询秒杀商品集合
	 *
	 * @param pageDTO 分页条件
	 */
	@Override
	public PageResultDTO findSkillCommoditys(PageDTO pageDTO){
        pageDTO.setStartIndex((pageDTO.getCurrentPage()-1)*pageDTO.getPageSize());
		//TODO:请在此校验参数的合法性
		this.validateFindSkillCommoditys(pageDTO);
		if(this.getStoreId()!=-1){
			pageDTO.getFilters().put("shopId",this.getStoreId());
			pageDTO.getFilterTypes().put("shopId","单值");
		}
		List<SkillCommodity> skillCommodityDTOS = this.skillCommodityDao.findSkillCommoditys(pageDTO);
		Long totalCount = this.skillCommodityDao.findSkillCommodityTotalCount(pageDTO);

		PageResultDTO pageResultDTO = new PageResultDTO();
		pageResultDTO.setTotalCount(totalCount);
		pageResultDTO.setDatas(skillCommodityDTOS);

		return pageResultDTO;
	}

	/**
	 * 查询全部秒杀商品集合
	 *
	 */
	@Override
	public List<SkillCommodity> findAllSkillCommoditys(){
		return this.skillCommodityDao.findAllSkillCommoditys();
	}

	/**
	 * 查询所有秒杀商品集合(只提取ID 和 Name)
	 *
	 */
	@Override
	public List<SkillCommodity> findAllSkillCommoditysWithIdName(){
		//TODO:请在此校验参数的合法性
		this.validateFindAllSkillCommoditysWithIdName();
		return this.skillCommodityDao.findAllSkillCommoditysWithIdName();
	}

	/**
	 * 根据名称查询秒杀商品集合(只提取ID 和 Name)
	 *
	 * @param skillCommodityName 名称
	 */
	@Override
	public List<SkillCommodity> findSkillCommoditysWithIdNameByName(String skillCommodityName){
		//TODO:请在此校验参数的合法性
		this.validateFindSkillCommoditysWithIdNameByName(skillCommodityName);
		//TODO:缓存取对应参数
		Set<String> keys = stringRedisTemplate.keys("searchData:SkillCommodity_where_skillCommodityName_" + skillCommodityName);
		List<SkillCommodity> skillCommoditys = new ArrayList<>();
		if (keys.isEmpty()) {
		skillCommoditys = this.skillCommodityDao.findSkillCommoditysWithIdNameByName(skillCommodityName);
		redisTemplate.opsForValue().set("searchData:SkillCommodity_where_skillCommodityName_" + skillCommodityName, skillCommoditys, 30, TimeUnit.DAYS);
		} else {
		skillCommoditys = redisTemplate.opsForValue().get("searchData:SkillCommodity_where_skillCommodityName_" + skillCommodityName);
		}
		return skillCommoditys;
	}

	/**
	 * 根据ID查询指定的秒杀商品(只提取ID 和 Name)
	 *
	 * @param skillCommodityId Id
	 */
	@Override
	public SkillCommodity findSkillCommoditysWithIdNameById(Long skillCommodityId){
		//TODO:请在此校验参数的合法性
		this.validateFindSkillCommoditysWithIdNameById(skillCommodityId);
		return this.skillCommodityDao.findSkillCommoditysWithIdNameById(skillCommodityId);
	}

	/**
	 * 根据ID查询指定的秒杀商品
	 *
	 * @param skillCommodityId Id
	 */
	@Override
	public SkillCommodity findSkillCommodity(Long skillCommodityId){
		//TODO:请在此校验参数的合法性
		this.validateFindSkillCommodity(skillCommodityId);
		return this.skillCommodityDao.findSkillCommodity(skillCommodityId);
	}

	/**
	 * 根据ID查询指定的秒杀商品(包含外键)
	 *
	 * @param skillCommodityId Id
	 */
	@Override
	public SkillCommodity findSkillCommodityWithForeignName(Long skillCommodityId){
		//TODO:请在此校验参数的合法性
		this.validateFindSkillCommodityWithForeignName(skillCommodityId);
		SkillCommodity skillCommodity= (SkillCommodity) redisUtil.get("skillCommodity"+skillCommodityId);
		if(skillCommodity!=null){
			return skillCommodity;
		}else{
			skillCommodity=this.skillCommodityDao.findSkillCommodityWithForeignName(skillCommodityId);
			redisUtil.set("skillCommodity"+skillCommodityId,skillCommodity,60*60);
		}
		return skillCommodity;
	}

	/**
	 * 新增秒杀商品
	 *
	 * @param skillCommodity 实体对象
	 */
	@Override
	public SkillCommodity saveSkillCommodity(SkillCommodity skillCommodity){
		//TODO:请在此校验参数的合法性
		this.validateSaveSkillCommodity(skillCommodity);
		//TODO:填充公共参数
		this.setSavePulicColumns(skillCommodity);
		skillCommodity.setShopId(this.getStoreId());
		Long rows = this.skillCommodityDao.saveSkillCommodity(skillCommodity);
		if(rows != 1)
		{
			String error = "新增保存秒杀商品出错，数据库应该返回1,但返回了 "+rows;
			throw new BusinessException(error);
		}
		return skillCommodity;
	}

	/**
	 * 更新秒杀商品
	 *
	 * @param skillCommodity 实体对象
	 */
	@Override
	public SkillCommodity updateSkillCommodity(SkillCommodity skillCommodity){
		//TODO:请在此校验参数的合法性
		this.validateUpdateSkillCommodity(skillCommodity);
		Long rows = this.skillCommodityDao.updateSkillCommodity(skillCommodity);
		if(rows != 1)
		{
			String error = "修改保存秒杀商品出错，数据库应该返回1,但返回了 "+rows+",数据可能被删除";
			throw new BusinessException(error);
		}
		return skillCommodity;
	}

	/**
	 * 根据ID删除秒杀商品
	 *
	 * @param skillCommodityId ID
	 */
	@Override
	public void deleteSkillCommodity(Long skillCommodityId){
		//TODO:请在此校验参数的合法性
		this.validateDeleteSkillCommodity(skillCommodityId);

		Map<Class<? extends BaseEntity>,EntityUsage> entityUsageMap = this.checkForeignEntity(SkillCommodity.class, skillCommodityId);
		if(entityUsageMap != null && entityUsageMap.size() >0){
			StringBuilder errors = new StringBuilder();
			errors.append("计划删除的数据正在被以下数引用\n");
			for(EntityUsage entityUsage : entityUsageMap.values()){
				errors.append("\t").append(entityUsage.getEntityLabel()).append("\n");
				for(Map.Entry<Long,String> entry : entityUsage.getUsageIdNames().entrySet() ){
					errors.append("\t\t").append(entry.getKey()).append("\t").append(entry.getValue()).append("\n");
				}
			}
			errors.append("，不能删除，请检查处理后再删除");
			throw  new BusinessException(errors.toString());
		}

		Long rows = this.skillCommodityDao.deleteSkillCommodity(skillCommodityId);
		if(rows != 1){
			String error = "删除秒杀商品出错，数据可能已经被删除";
			throw new BusinessException(error);
		}
	}

	@Autowired
	private RedissonUtil redissonUtil;
	@Autowired
	private OrderDao orderDao;
	@Autowired
	private RabbitMqUtil rabbitMqUtil;

	@Override
	public JsonResult skillOrder(Order order) {
			    /*
                1、直接在数据库新增数据，在用户量较大时，会造成数据库压力过大导致异常

                2、优化
                    2.1、使用redis存储商品库存，下单时进行减去相对应库存
                    2.2、扣减库存时，通过redis校验是否超卖，超卖直接结束请求，返回结果，未超卖，执行下部分流程
                    2.3、redis扣减库存，此时返回结果，下单成功（提升用户体验），然后加入Rabbit等待，若超时未支付则库存+1，否则完成订单的提交

            */
		//
		Long userId = this.getLoginUserId();
		if(userId==-1){
			userId=order.getUserId();
		}
		order.setUserId(userId);
		//限制用户不能连续多次点击
		String clickNumKey=order.getUserId()+"";
		Integer clickNum= (Integer) redisUtil.get(clickNumKey);
		if(clickNum==null){
			redisUtil.set(clickNumKey,1,30);
		}else if(clickNum<=5){
			redisUtil.incr(clickNumKey,1);
		}else{
			return JsonResult.error("请勿频繁点击,请30s后再重试");
		}
		//限制同一个用户不能下两次单
		String doneBuyKey="skill-"+order.getCommodityId()+"-"+order.getUserId();
		if(redisUtil.hasKey(doneBuyKey)){
			return JsonResult.error("你已经下单过了,不可重复抢购");
		}
		String commodityStockKey = "skill-"+order.getCommodityId()+"-"+order.getSpecification();

		//查询不到库存则去数据进行查询，此时需要进行加锁，防止大量请求直接访问数据，其余线程发现已经加锁，则自旋，直到返回库存
		//redisson实现分布式锁
		String lockKey="skill-lock-" + commodityStockKey+"-"+order.getSkillCommodityId();
		redissonUtil.lock(lockKey);
		logger.info(order.getUserId()+"-加锁成功-"+lockKey);
		try{
			//如果业务执行过长，Redisson会自动给锁续期
			Integer stock = (Integer) this.redisUtil.get(commodityStockKey);
			if(stock==null){
				//获取秒杀商品的库存
				stock=this.skillCommodityDao.getStock(order.getSkillCommodityId());
				if(stock==null){
					stock=0;
				}
				this.redisUtil.set(commodityStockKey,stock,3600);
				logger.info("重新加载了库存"+commodityStockKey+":"+stock);
			}
			if(stock<=0){
				return JsonResult.error("不好意思，秒杀已经结束了");
			}else{
				//redis中扣减库存
				this.redisUtil.decr(commodityStockKey,1);

				//数据库扣减库存  --优化思路==》利用rabbitmq异步操作数据库
				Long rows=this.skillCommodityDao.decrStock(order.getSkillCommodityId(),1);
				if(rows==1) {
					//生成订单
					order.setCode("D" + PrimaryGenerator.getInstance().make());
					order.setCreateDatetime(new Date());
					order.setOrderStatus("待付款");
					//获取用户收获地址信息
					ShippingAddress userAddress = order.getUserAddress();
					order.setRecipient(userAddress.getRecipient());
					order.setPhone(userAddress.getPhone());
					order.setDetailedAddress(userAddress.getArea()+userAddress.getDetailedAddress());
					//保存订单
					this.orderDao.saveOrder(order);
					rabbitMqUtil.sendMessage(order);
					logger.info(order.getUserId()+":秒杀成功");
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
			this.redisUtil.incr(commodityStockKey,1);
			throw new BusinessException("提交订单出错了！请稍后重试");
		} finally {
			//解锁，如果业务执行完成，就不会继续续期，即使没有手动释放锁，在30秒过后，也会释放锁
			redissonUtil.unlock(lockKey);
			logger.info(order.getUserId()+"解锁成功");
		}
		redisUtil.set(doneBuyKey,order.getCommodityId());
		return JsonResult.ok("秒杀成功，请及时付款").put("orderId",order.getEid());
	}


	@Override
	public int incrStock(Long skillCommodityId, int count) {
		return this.skillCommodityDao.incrStock(skillCommodityId,count);
	}

	@Override
	public JsonResult putSkill(Long skillCommodityId) {
		return JsonResult.ok();
	}

	//TODO:---------------验证-------------------

	private void validateFindSkillCommoditys(PageDTO pageDTO) {
	//TODO:请使用下面方法添加数据过滤条件
	//		pageDTO.addFilter("creatorId",this.getLoginUserId());
	//TODO:请完善数据校验规则和数据权限判断，如果有问题请抛出异常，参看下面validateUpdateSkillCommodity()写法
	}

	private void validateFindSkillCommoditysWithIdNameByName(String skillCommodityName) {
	//TODO:请完善数据校验规则和数据权限判断，如果有问题请抛出异常，参看下面validateUpdateSkillCommodity()写法
	}


	private void validateFindAllSkillCommoditysWithIdName() {
	//TODO:请完善数据校验规则和数据权限判断，如果有问题请抛出异常，参看下面validateUpdateSkillCommodity()写法
	}

	private void validateFindSkillCommoditysWithIdNameById(Long skillCommodityId) {
	//TODO:请完善数据校验规则和数据权限判断，如果有问题请抛出异常，参看下面validateUpdateSkillCommodity()写法
	}

	private void validateFindSkillCommodity(Long skillCommodityId) {
	//TODO:请完善数据校验规则和数据权限判断，如果有问题请抛出异常，参看下面validateUpdateSkillCommodity()写法
	}

	private void validateFindSkillCommodityWithForeignName(Long skillCommodityId) {
	//TODO:请完善数据校验规则和数据权限判断，如果有问题请抛出异常，参看下面validateUpdateSkillCommodity()写法
	}

	private void validateSaveSkillCommodity(SkillCommodity skillCommodity) {
	//不为空判断
	if (skillCommodity.getEid() != null || skillCommodity.getCreatorId() != null || skillCommodity.getCreateDatetime() != null) {
	throw new BusinessException("非法请求");
	}
	//TODO:请完善数据校验规则和数据权限判断，如果有问题请抛出异常，参看下面validateUpdateSkillCommodity()写法
	}

	private void validateUpdateSkillCommodity(SkillCommodity skillCommodity) {
	//不为空判断
	if (skillCommodity.getEid() == null) {
	throw new BusinessException("唯一标识不能为空");
	}
	//是否存在判断
	if (this.skillCommodityDao.findSkillCommodityTotalCount(PageDTO.create(SkillCommodity.FIELD_ID, skillCommodity.getEid())) == 0) {
	throw new BusinessException("修改的秒杀商品 " + skillCommodity.getName() + " 不存在，修改失败，请重试或联系管理员");
	}
	//TODO:请完善数据校验规则和数据权限判断，如果有问题请抛出异常，参看下面validateUpdateSkillCommodity()写法
	}

	private void validateDeleteSkillCommodity(Long skillCommodityId) {
	//TODO:请完善数据校验规则和数据权限判断，如果有问题请抛出异常，参看下面validateUpdateSkillCommodity()写法
	}

	@Override
	public boolean canDownloadAttachment(String formName, Long id) {
	return true;
	}
}
